
/*
 * Copyright (C) Xiaozhe Wang (chaoslawful)
 * Copyright (C) Yichun Zhang (agentzh)
 */


#ifndef DDEBUG
#define DDEBUG 0
#endif
#include "ddebug.h"


#include "ngx_http_lua_log.h"
#include "ngx_http_lua_util.h"
#include "ngx_http_lua_log_ringbuf.h"


static int  ngx_http_lua_print(lua_State *L);
static int  ngx_http_lua_ngx_log(lua_State *L);
static int  log_wrapper(ngx_log_t *log, const char *ident, ngx_uint_t level,
                        lua_State *L);
static void ngx_http_lua_inject_log_consts(lua_State *L);


/**
 * Wrapper of nginx log functionality. Take a log level param and varargs of
 * log message params.
 *
 * @param L Lua state pointer
 * @retval always 0 (don't return values to Lua)
 * */
int
ngx_http_lua_ngx_log(lua_State *L)
{
    ngx_log_t          *log;
    ngx_http_request_t *r;
    const char         *msg;
    int                 level;

    r = ngx_http_lua_get_req(L);

    if (r && r->connection && r->connection->log)
    {
        log = r->connection->log;
    }
    else
    {
        log = ngx_cycle->log;
    }

    level = luaL_checkint(L, 1);
    if (level < NGX_LOG_STDERR || level > NGX_LOG_DEBUG)
    {
        msg = lua_pushfstring(L, "bad log level: %d", level);
        return luaL_argerror(L, 1, msg);
    }

    /* remove log-level param from stack */
    lua_remove(L, 1);

    return log_wrapper(log, "[lua] ", (ngx_uint_t)level, L);
}


/**
 * Override Lua print function, output message to nginx error logs. Equal to
 * ngx.log(ngx.NOTICE, ...).
 *
 * @param L Lua state pointer
 * @retval always 0 (don't return values to Lua)
 * */
int
ngx_http_lua_print(lua_State *L)
{
    ngx_log_t          *log;
    ngx_http_request_t *r;

    r = ngx_http_lua_get_req(L);

    if (r && r->connection && r->connection->log)
    {
        log = r->connection->log;
    }
    else
    {
        log = ngx_cycle->log;
    }

    return log_wrapper(log, "[lua] ", NGX_LOG_NOTICE, L);
}


static int
log_wrapper(ngx_log_t *log, const char *ident, ngx_uint_t level, lua_State *L)
{
    u_char     *buf;
    u_char     *p, *q;
    ngx_str_t   name;
    int         nargs, i;
    size_t      size, len;
    size_t      src_len = 0;
    int         type;
    const char *msg;
    lua_Debug   ar;

    if (level > log->log_level)
    {
        return 0;
    }

#if 1
    /* add debug info */

    lua_getstack(L, 1, &ar);
    lua_getinfo(L, "Snl", &ar);

    /* get the basename of the Lua source file path, stored in q */
    name.data = (u_char *)ar.short_src;
    if (name.data == NULL)
    {
        name.len = 0;
    }
    else
    {
        p = name.data;
        while (*p != '\0')
        {
            if (*p == '/' || *p == '\\')
            {
                name.data = p + 1;
            }
            p++;
        }

        name.len = p - name.data;
    }

#endif

    nargs = lua_gettop(L);

    size = name.len + NGX_INT_T_LEN + sizeof(":: ") - 1;

    if (*ar.namewhat != '\0' && *ar.what == 'L')
    {
        src_len = ngx_strlen(ar.name);
        size += src_len + sizeof("(): ") - 1;
    }

    for (i = 1; i <= nargs; i++)
    {
        type = lua_type(L, i);
        switch (type)
        {
        case LUA_TNUMBER:
        case LUA_TSTRING:
            lua_tolstring(L, i, &len);
            size += len;
            break;

        case LUA_TNIL: size += sizeof("nil") - 1; break;

        case LUA_TBOOLEAN:
            if (lua_toboolean(L, i))
            {
                size += sizeof("true") - 1;
            }
            else
            {
                size += sizeof("false") - 1;
            }

            break;

        case LUA_TTABLE:
            if (!luaL_callmeta(L, i, "__tostring"))
            {
                return luaL_argerror(L, i,
                                     "expected table to have "
                                     "__tostring metamethod");
            }

            lua_tolstring(L, -1, &len);
            size += len;
            break;

        case LUA_TLIGHTUSERDATA:
            if (lua_touserdata(L, i) == NULL)
            {
                size += sizeof("null") - 1;
                break;
            }

            continue;

        default:
            msg = lua_pushfstring(L,
                                  "string, number, boolean, or nil "
                                  "expected, got %s",
                                  lua_typename(L, type));
            return luaL_argerror(L, i, msg);
        }
    }

    buf = lua_newuserdata(L, size);

    p = ngx_copy(buf, name.data, name.len);

    *p++ = ':';

    p = ngx_snprintf(p, NGX_INT_T_LEN, "%d",
                     ar.currentline ? ar.currentline : ar.linedefined);

    *p++ = ':';
    *p++ = ' ';

    if (*ar.namewhat != '\0' && *ar.what == 'L')
    {
        p    = ngx_copy(p, ar.name, src_len);
        *p++ = '(';
        *p++ = ')';
        *p++ = ':';
        *p++ = ' ';
    }

    for (i = 1; i <= nargs; i++)
    {
        type = lua_type(L, i);
        switch (type)
        {
        case LUA_TNUMBER:
        case LUA_TSTRING:
            q = (u_char *)lua_tolstring(L, i, &len);
            p = ngx_copy(p, q, len);
            break;

        case LUA_TNIL:
            *p++ = 'n';
            *p++ = 'i';
            *p++ = 'l';
            break;

        case LUA_TBOOLEAN:
            if (lua_toboolean(L, i))
            {
                *p++ = 't';
                *p++ = 'r';
                *p++ = 'u';
                *p++ = 'e';
            }
            else
            {
                *p++ = 'f';
                *p++ = 'a';
                *p++ = 'l';
                *p++ = 's';
                *p++ = 'e';
            }

            break;

        case LUA_TTABLE:
            luaL_callmeta(L, i, "__tostring");
            q = (u_char *)lua_tolstring(L, -1, &len);
            p = ngx_copy(p, q, len);
            break;

        case LUA_TLIGHTUSERDATA:
            *p++ = 'n';
            *p++ = 'u';
            *p++ = 'l';
            *p++ = 'l';

            break;

        default: return luaL_error(L, "impossible to reach here");
        }
    }

    if (p - buf > (off_t)size)
    {
        return luaL_error(L, "buffer error: %d > %d", (int)(p - buf),
                          (int)size);
    }

    ngx_log_error(level, log, 0, "%s%*s", ident, (size_t)(p - buf), buf);

    return 0;
}


void
ngx_http_lua_inject_log_api(lua_State *L)
{
    ngx_http_lua_inject_log_consts(L);

    lua_pushcfunction(L, ngx_http_lua_ngx_log);
    lua_setfield(L, -2, "log");

    lua_pushcfunction(L, ngx_http_lua_print);
    lua_setglobal(L, "print");
}


static void
ngx_http_lua_inject_log_consts(lua_State *L)
{
    /* {{{ nginx log level constants */
    lua_pushinteger(L, NGX_LOG_STDERR);
    lua_setfield(L, -2, "STDERR");

    lua_pushinteger(L, NGX_LOG_EMERG);
    lua_setfield(L, -2, "EMERG");

    lua_pushinteger(L, NGX_LOG_ALERT);
    lua_setfield(L, -2, "ALERT");

    lua_pushinteger(L, NGX_LOG_CRIT);
    lua_setfield(L, -2, "CRIT");

    lua_pushinteger(L, NGX_LOG_ERR);
    lua_setfield(L, -2, "ERR");

    lua_pushinteger(L, NGX_LOG_WARN);
    lua_setfield(L, -2, "WARN");

    lua_pushinteger(L, NGX_LOG_NOTICE);
    lua_setfield(L, -2, "NOTICE");

    lua_pushinteger(L, NGX_LOG_INFO);
    lua_setfield(L, -2, "INFO");

    lua_pushinteger(L, NGX_LOG_DEBUG);
    lua_setfield(L, -2, "DEBUG");
    /* }}} */
}


#ifdef HAVE_INTERCEPT_ERROR_LOG_PATCH
ngx_int_t
ngx_http_lua_capture_log_handler(ngx_log_t *log, ngx_uint_t level, u_char *buf,
                                 size_t n)
{
    ngx_http_lua_log_ringbuf_t *ringbuf;

    dd("enter");

    ringbuf = (ngx_http_lua_log_ringbuf_t *)ngx_cycle->intercept_error_log_data;

    if (level > ringbuf->filter_level)
    {
        return NGX_OK;
    }

    ngx_http_lua_log_ringbuf_write(ringbuf, level, buf, n);

    dd("capture log: %s\n", buf);

    return NGX_OK;
}
#endif


#ifndef NGX_LUA_NO_FFI_API
int
ngx_http_lua_ffi_errlog_set_filter_level(int level, u_char *err, size_t *errlen)
{
#ifdef HAVE_INTERCEPT_ERROR_LOG_PATCH
    ngx_http_lua_log_ringbuf_t *ringbuf;

    ringbuf = ngx_cycle->intercept_error_log_data;

    if (ringbuf == NULL)
    {
        *errlen = ngx_snprintf(err, *errlen,
                               "directive \"lua_capture_error_log\" is not set")
                  - err;
        return NGX_ERROR;
    }

    if (level > NGX_LOG_DEBUG || level < NGX_LOG_STDERR)
    {
        *errlen = ngx_snprintf(err, *errlen, "bad log level: %d", level) - err;
        return NGX_ERROR;
    }

    ringbuf->filter_level = level;
    return NGX_OK;
#else
    *errlen = ngx_snprintf(err, *errlen,
                           "missing the capture error log patch for nginx")
              - err;
    return NGX_ERROR;
#endif
}


int
ngx_http_lua_ffi_errlog_get_msg(char **log, int *loglevel, u_char *err,
                                size_t *errlen, double *log_time)
{
#ifdef HAVE_INTERCEPT_ERROR_LOG_PATCH
    ngx_uint_t loglen;

    ngx_http_lua_log_ringbuf_t *ringbuf;

    ringbuf = ngx_cycle->intercept_error_log_data;

    if (ringbuf == NULL)
    {
        *errlen = ngx_snprintf(err, *errlen,
                               "directive \"lua_capture_error_log\" is not set")
                  - err;
        return NGX_ERROR;
    }

    if (ringbuf->count == 0)
    {
        return NGX_DONE;
    }

    ngx_http_lua_log_ringbuf_read(ringbuf, loglevel, (void **)log, &loglen,
                                  log_time);
    return loglen;
#else
    *errlen = ngx_snprintf(err, *errlen,
                           "missing the capture error log patch for nginx")
              - err;
    return NGX_ERROR;
#endif
}


int
ngx_http_lua_ffi_errlog_get_sys_filter_level(ngx_http_request_t *r)
{
    ngx_log_t *log;
    int        log_level;

    if (r && r->connection && r->connection->log)
    {
        log = r->connection->log;
    }
    else
    {
        log = ngx_cycle->log;
    }

    log_level = log->log_level;
    if (log_level == NGX_LOG_DEBUG_ALL)
    {
        log_level = NGX_LOG_DEBUG;
    }

    return log_level;
}


int
ngx_http_lua_ffi_raw_log(ngx_http_request_t *r, int level, u_char *s,
                         size_t s_len)
{
    ngx_log_t *log;

    if (level > NGX_LOG_DEBUG || level < NGX_LOG_STDERR)
    {
        return NGX_ERROR;
    }

    if (r && r->connection && r->connection->log)
    {
        log = r->connection->log;
    }
    else
    {
        log = ngx_cycle->log;
    }

    ngx_log_error((unsigned)level, log, 0, "%*s", s_len, s);

    return NGX_OK;
}

#endif

/* vi:set ft=c ts=4 sw=4 et fdm=marker: */
